/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.common.util;

import java.lang.reflect.*;

/**
 * This class provides a dynamic class loading facility. It is
 * tightly coupled to the property facility. To dynamically obtain
 * an instance of a class through its constructor:
 *
 * <pre>
 * Integer i = null;
 * DynamicLoader dl = new DynamicLoader( "java.lang.Integer" );
 * try {
 *   // instantiate as Integer("42")
 *   String arg[] = new String[1];
 *   arg[0] = "42";
 *   i = (Integer) dl.instantiate(arg);
 * } catch ( Exception e ) {
 *   System.err.println( dl.convertException(e) );
 *   System.exit(1);
 * }
 * </pre>
 *
 * Similarily, to obtain an instance of a class through a static
 * method provided by the same class, or another class:
 *
 * <pre>
 * Integer i = null;
 * DynamicLoader dl = new DynamicLoader( "java.lang.Integer" );
 * try {
 *   // instantiate as Integer("42")
 *   String arg[] = new String[1];
 *   arg[0] = "42";
 *   i = (Integer) dl.static_method( "valueOf", arg );
 * } catch ( Exception e ) {
 *   System.err.println( dl.convertException(e) );
 *   System.exit(1);
 * }
 * </pre>

 *
 * @author Karan Vahi
 * @author Jens-S. Vöckler
 * @version $Revision$
 */
public class DynamicLoader
{
  /**
   * Stores the fully qualified class name to dynamically instantiate.
   */
  private String m_classname;

  /**
   *
   */
  public DynamicLoader( String classname )
  {
    if ( (this.m_classname = classname) == null )
      throw new NullPointerException( "You must specify a fully-qualified class name" );
  }

  /**
   * Sets the fully-qualified class name to load.
   * @param classname is the new class name.
   * @see #getClassName()
   */
  public void setClassName( String classname )
  {
    if ( (this.m_classname = classname) == null )
      throw new NullPointerException( "You must specify a fully-qualified class name" );
  }

  /**
   * Obtains the fully-qualified class name that this instance works with.
   * @return the class name.
   * @see #setClassName( String )
   */
  public String getClassName()
  { return this.m_classname; }

  /**
   * Dynamically instantiates a class from a contructor. You must have
   * set the class name before invoking this method. Please note that
   * any exceptions thrown by the constructor will be wrapped into a
   * <code>InvocationTargetException</code>.
   *
   * @param arguments are arguments to the constructor of the class
   * to load. Please use "new Object[0]" for the argumentless default
   * constructor.
   * @return an instance that must be cast to the correct class.
   *
   * @exception ClassNotFoundException if the driver for the database
   * cannot be loaded. You might want to check your CLASSPATH, too.
   * @exception NoSuchMethodException if the driver's constructor interface
   * does not comply with the database driver API.
   * @exception InstantiationException if the driver class is an abstract
   * class instead of a concrete implementation.
   * @exception IllegalAccessException if the constructor for the driver
   * class it not publicly accessible to this package.
   * @exception InvocationTargetException if the constructor of the driver
   * throws an exception while being dynamically loaded.
   * @exception SQLException if the driver for the database can be
   * loaded, but faults when initially accessing the database
   *
   * @see #setClassName( String )
   */
  public Object instantiate( Object[] arguments )
    throws ClassNotFoundException,
	   NoSuchMethodException, InstantiationException,
	   IllegalAccessException, InvocationTargetException
  {
    // generate class array and populate with class of each argument
    Class[] temp = new Class[ arguments.length ];
    for ( int i=0; i<arguments.length; ++i )//Amelie: temp[0]=properties,temp[1]=dax;
      temp[i] = arguments[i].getClass();

    // load class into memory and obtain an instance of it
    return Class.forName(m_classname).getConstructor(temp)
      .newInstance(arguments); //Amelie: create a Text class instance, use its default constructor to construct a new instance
  }

  /**
   * Dynamically instantiates a class from a contructor. You must have
   * set the class name before invoking this method. Please note that
   * any exceptions thrown by the constructor will be wrapped into a
   * <code>InvocationTargetException</code>.<p>
   * This method should be invoked, if the constructor declares
   * interface types as formal arguments, but the actual arguments
   * are implementation classes.
   *
   * @param classes is a vector of the classes involved. Each item
   * in the classes vector matches the item in the arguments vector.
   * The classes vector will be used to select the correct constructor.
   * Please use "new Class[0]" for the argumentless default ctor.
   * @param arguments are arguments to the constructor of the class
   * to load. Please use "new Object[0]" for the argumentless default
   * constructor.
   * @return an instance that must be cast to the correct class.
   *
   * @exception ClassNotFoundException if the driver for the database
   * cannot be loaded. You might want to check your CLASSPATH, too.
   * @exception NoSuchMethodException if the driver's constructor interface
   * does not comply with the database driver API.
   * @exception IllegalArgumentException is thrown, if the number of
   * arguments do not match the number of types, ie the vector have
   * mismatching sizes.
   * @exception InstantiationException if the driver class is an abstract
   * class instead of a concrete implementation.
   * @exception IllegalAccessException if the constructor for the driver
   * class it not publicly accessible to this package.
   * @exception InvocationTargetException if the constructor of the driver
   * throws an exception while being dynamically loaded.
   * @exception SQLException if the driver for the database can be
   * loaded, but faults when initially accessing the database
   *
   * @see #setClassName( String )
   */
  public Object instantiate( Class[] classes, Object[] arguments )
    throws ClassNotFoundException,
	   NoSuchMethodException, InstantiationException,
	   IllegalAccessException, InvocationTargetException
  {
    // complain on argument mismatch
    if ( classes.length != arguments.length )
      throw new IllegalArgumentException( "vector sizes must match" );

    // load class into memory and obtain an instance of it
    return Class.forName(m_classname).getConstructor(classes)
      .newInstance(arguments);
  }

  /**
   * Dynamically instantiates a class from a static method which
   * constructs the resulting object. You must have set the class name
   * before invoking this method. Please note that any exceptions thrown
   * by the constructor will be wrapped into a
   * <code>InvocationTargetException</code>.
   *
   * @param method is the name of the static method to call.
   * @param arguments are arguments to the constructor of the class
   * to load. Please use "new Object[0]" for the argumentless default
   * constructor.
   * @return an instance that must be cast to the correct class.
   *
   * @exception ClassNotFoundException if the driver for the database
   * cannot be loaded. You might want to check your CLASSPATH, too.
   * @exception NoSuchMethodException if the driver's constructor interface
   * does not comply with the database driver API.
   * @exception InstantiationException if the driver class is an abstract
   * class instead of a concrete implementation.
   * @exception IllegalAccessException if the constructor for the driver
   * class it not publicly accessible to this package.
   * @exception InvocationTargetException if the constructor of the driver
   * throws an exception while being dynamically loaded.
   * @exception SQLException if the driver for the database can be
   * loaded, but faults when initially accessing the database
   * @exception SecurityException if you are not permitted to invoke the
   * method, or even list the methods provided by the class.
   * @exception NullPointerException if the method name is
   * <code>null</code>.
   * @exception IllegalArgumentException if the number of actual and
   * formal parameter differ, unwrapping a primitive type failed, or
   * a parameter value cannot be converted to the formal argument type.
   *
   * @see #setClassName( String )
   */
  public Object static_method( String method, Object[] arguments )
    throws ClassNotFoundException, SecurityException,
	   NoSuchMethodException, InstantiationException,
	   IllegalAccessException, InvocationTargetException,
	   NullPointerException, IllegalArgumentException
  {
    // generate class array and populate with class of each argument
    Class[] temp = new Class[ arguments.length ];
    for ( int i=0; i<arguments.length; ++i )
      temp[i] = arguments[i].getClass();

    // load the class into memory, and find the method, and invoke it as
    // a static method
    return Class.forName(m_classname).getDeclaredMethod( method, temp )
      .invoke( null, arguments );
  }


  /**
   * Converts an exception from the class loader into an error message.
   *
   * @param classname is the name or some other class signifier.
   * @param e is the exception thrown by the class loader.
   * @return a string that tries to describe what went wrong.
   */
  static public String convertException( String classname, Exception e )
  {
    String result = null;

    // check exceptions
    result = convertExceptionToString( classname, e );

    //Commented out, as defined in convertToString(String,Exception) function
    //Karan April 25, 2006
//    if ( e instanceof ClassNotFoundException ) {
//      result = "Unable to dynamically load " + classname;
//      // do cause
//    } else if ( e instanceof NoSuchMethodException ) {
//      result = "Unable to dynamically invoke the constructor of " + classname;
//      // no cause
//    } else if ( e instanceof InstantiationException ) {
//      result = "The dynamically loadable class " + classname + " is either " +
//	"abstract or an interface";
//      // no cause
//    } else if ( e instanceof IllegalAccessException ) {
//      result = "Unable to access appropriate constructor in " + classname;
//      // no cause
//    } else if ( e instanceof InvocationTargetException ) {
//      result = "Class " + classname + " threw exception " +
//	e.getClass().getName() + " during construction";
//      // do cause
//    } else if ( e instanceof IllegalArgumentException ) {
//      result = "Class " + classname + " threw exception " +
//	e.getClass().getName() + " during method invocation argument " +
//	"list construction";
//    } else if ( e instanceof NullPointerException ) {
//      result = "Invalid static initializer method name for " + classname;
//      // no cause
//    } else if ( e instanceof SecurityException ) {
//      result = "Prohibited access to " + classname;
//      // ?? cause
//    } else {
//      result = classname + " caugth " + e.getClass().getName();
//      // ?? cause
//    }

    // append complete cause chain
    int i = 0;
    for ( Throwable cause=e.getCause(); cause != null; cause=cause.getCause() ) {
      result += " [" + Integer.toString(++i) + "]: " + cause;
    }

    // done
    return result;
  }

  /**
   * Converts an exception from the class loader into an error message.
   *
   * @param e is the exception thrown by the class loader.
   * @return a string that tries to describe what went wrong.
   */
  public String convertException( Exception e )
  { return DynamicLoader.convertException( m_classname, e ); }

  /**
   * Converts an exception from the class loader into an error message.
   * Note: It does not convert any cause messages.
   *
   * @param classname is the name or some other class signifier.
   * @param e is the exception thrown by the class loader.
   * @return a string that tries to describe what went wrong.
   */
  static public String convertExceptionToString( String classname, Throwable e )
  {
    String result = null;

    // check exceptions
    if ( e instanceof ClassNotFoundException ) {
      result = "Unable to dynamically load " + classname;
      // do cause
    } else if ( e instanceof NoSuchMethodException ) {
      result = "Unable to dynamically invoke the constructor " + e.getMessage() ;
      // no cause
    } else if ( e instanceof InstantiationException ) {
      result = "The dynamically loadable class " + classname + " is either " +
        "abstract or an interface";
      // no cause
    } else if ( e instanceof IllegalAccessException ) {
      result = "Unable to access appropriate constructor in " + classname;
      // no cause
    } else if ( e instanceof InvocationTargetException ) {
      result = "Class " + classname + " threw exception " +
        e.getClass().getName() + " during construction";
      // do cause
    } else if ( e instanceof IllegalArgumentException ) {
      result = "Class " + classname + " threw exception " +
        e.getClass().getName() + " during method invocation argument " +
        "list construction";
    } else if ( e instanceof NullPointerException ) {
      result = "Invalid static initializer method name for " + classname;
      // no cause
    } else if ( e instanceof SecurityException ) {
      result = "Prohibited access to " + classname;
      // ?? cause
    } else {
      result = classname + " caught " + e.getClass().getName() + " " + e.getMessage();
      // ?? cause
    }

    // done
    return result;
  }

}
